package com.lagou.edu.factory;

import com.lagou.edu.annotation.Autowired;
import com.lagou.edu.annotation.Service;
import com.lagou.edu.annotation.Transactional;
import org.reflections.Reflections;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * @author 应癫
 * <p>
 * 工厂类，生产对象（使用反射技术）
 */
public class BeanFactory {

    /**
     * 任务一：扫包，完成带有@Service注解类的实例化，，通过反射技术实例化对象并且存储待用（map集合）
     * 任务二：对外提供获取实例对象的接口（根据id获取）
     */
    private static Map<String, Object> map = new HashMap<>();  // 存储对象

    public static void init(String basePackage) throws IllegalAccessException, InstantiationException {
            // 1.通过扫包，找到所有带有@Service注解的类，这些类需要加入IOC容器管理，需要进行实例化
            Set<Class<?>> classes = findAnnotatedClasses(basePackage, Service.class);
            for (Class<?> c : classes) {
                // 为指定类生成id 如果@Serice注解value值不为空，则以value为id，否则以首字母转换为小写后的类名为id。
                String id = getClassId(c);
                // 通过反射调用无参构造器创建对象，并加入到容器中（此时仅仅是完成实例化，还没有完成属性注入）
                map.put(id, c.newInstance());
            }
            // 2.完成bean的初始化工作，包括属性的自动注入，以及为需要事务控制的对象生成代理对象等
            finishBeanInitialization();
    }

    /**
     * 扫描指定包及其子包下所有带有指定注解的类，返回找到的Class对象集合
     *
     * @param packageName     指定包
     * @param annotationClass 指定注解
     * @return
     */
    private static Set<Class<?>> findAnnotatedClasses(String packageName, Class<? extends Annotation> annotationClass) {
        return new Reflections(packageName).getTypesAnnotatedWith(annotationClass);
    }

    private static void finishBeanInitialization() throws IllegalAccessException {
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            // 获取到每个bean
            Object o = entry.getValue();
            // bean对应的类模板
            Class c = o.getClass();
            // 完成依赖属性的自动注入，
            // 目前的这种处理方式，我们的map既是单例池，又相当于spring的三级缓存，即使没有完全初始化完成的bean也可以从map中获取到
            Field[] fields = c.getDeclaredFields();
            // 遍历类中的所有属性
            for (Field field : fields) {
                // 若属性带有@Autowired注解则需要进行注入
                if (field.isAnnotationPresent(Autowired.class)) {
                    // 属性对应的类型
                    Class<?> fieldClass = field.getType();
                    // 属性对应的全限定类名
                    String fieldFullClassName = fieldClass.getName();
                    String[] splitArr = fieldFullClassName.split("\\.");
                    // 属性名
                    String fieldName = field.getName();
                    // 根据@Autowired注解标识的属性所对应的类型，从IOC容器中取对象，若取不到到则注入失败抛出异常
                    Object field2Inject = getBean(fieldClass);
                    if (field2Inject == null) {
                        throw new IllegalArgumentException(String.format("无法为类型为[%s]的对象自动注入类型为[%s]的属性[%s]", c.getName(), fieldFullClassName, fieldName));
                    }

                    field.setAccessible(true);
                    field.set(o, field2Inject);
                }
            }

            // 添加事务控制：判断对象类是否带有@Transactional注解，若有则修改对象为代理对象
            // 题目要求(2)：service层是否实现接口的情况【jdk还是cglib】
            if (c.isAnnotationPresent(Transactional.class)) {
                Object proxyObj;
                //获取代理工厂
                ProxyFactory proxyFactory = (ProxyFactory) BeanFactory.getBean("proxyFactory");
                //获取目标类实现的所有接口
                Class[] interfaces = c.getInterfaces();
                // 如果目标类实现了接口，那么用jdk动态代理即可（其实不管是否实现接口，都可以用cglib动态代理，这里我们用jdk动态代理）
                if (interfaces != null && interfaces.length > 0) {
                    proxyObj = proxyFactory.getJdkProxy(o);
                } else {
                    // 没实现接口则只能用cglib
                    proxyObj = proxyFactory.getCglibProxy(o);
                }
                // 把生成的代理对象放入IOC容器中，后续从容器中取到的就是可以完成事务控制的代理对象
                map.put(entry.getKey(), proxyObj);
            }

            // 不需要重新放入map，本来就是直接修改的map中所引用的对象的内容，并没有修改引用的地址
        }
    }

    private static String getClassId(Class<?> clazz) {
        String id = null;
        Service service = clazz.getAnnotation(Service.class);
        if (service != null) {
            id = service.value();
        }

        // 题目要求(1)：注解有无value属性值【@service（value=""）】
        if (id == null || id.equals("")) {
            String fullClassName = clazz.getName();
            String[] split = fullClassName.split("\\.");
            String className = split[split.length - 1];
            id = className.substring(0, 1).toLowerCase() + className.substring(1);
        }

        return id;
    }

    /**
     * 按照类型获取bean
     * @param clz
     * @return
     */
    private static Object getBean(Class<?> clz) {
        for (Object o : map.values()) {
            Class oClass = o.getClass();
            // 如果当前对象的类型跟目标类型相同，或者是目标接口的实现类，则匹配成功
            if (oClass == clz || clz.isAssignableFrom(oClass)) {
                return o;
            }
        }
        return null;
    }

    // 任务二：对外提供获取实例对象的接口（根据id获取）
    public static Object getBean(String id) {
        return map.get(id);
    }
}
